package com.wbl.train.business.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wbl.train.business.service.*;
import com.wbl.train.business.service.transactional.AfterConfirmOrderTransactional;
import com.wbl.train.business.mq.producer.BeforeConfirmOrder;
import com.wbl.train.common.business.dto.ConfirmOrderDto;
import com.wbl.train.common.business.dto.ConfirmOrderTicket;
import com.wbl.train.common.business.entity.*;
import com.wbl.train.business.mapper.ConfirmOrderMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wbl.train.common.business.enums.ConfirmOrderStatusEnum;
import com.wbl.train.common.business.enums.SeatColEnum;
import com.wbl.train.common.business.enums.SeatTypeEnum;
import com.wbl.train.common.exception.ApplicationException;
import com.wbl.train.common.member.entity.PassengerEntity;
import com.wbl.train.common.page.PageVo;
import com.wbl.train.common.redisson.enums.RedissonKeyPreEnum;
import com.wbl.train.common.result.ResultCode;
import com.wbl.train.common.utils.SnowUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 确认订单 服务实现类
 * @author 王博纶
 * @since 2024-03-04
 */
@Slf4j
@Service
public class ConfirmOrderServiceImpl extends ServiceImpl<ConfirmOrderMapper, ConfirmOrderEntity> implements ConfirmOrderService {

    @Resource
    private DailyTrainTicketService dailyTrainTicketService;
    @Resource
    private DailyTrainCarriageService dailyTrainCarriageService;
    @Resource
    private DailyTrainSeatService dailyTrainSeatService;
    @Resource
    private AfterConfirmOrderTransactional afterConfirmOrderTransactional;
    @Lazy
    @Resource
    private BeforeConfirmOrder beforeConfirmOrder;
    @Resource
    private RedissonClient redissonClient;

    @Override
    public PageVo<ConfirmOrderEntity> queryConfirmOrder(ConfirmOrderDto confirmOrderDto) {
        // 获取分页所需要的参数
        int page = confirmOrderDto.getPage();
        int limit = confirmOrderDto.getLimit();

        LambdaQueryWrapper<ConfirmOrderEntity> confirmOrderWrapper = new LambdaQueryWrapper<ConfirmOrderEntity>()
                .orderByDesc(ConfirmOrderEntity::getDate) // 按照date降序排列
                .orderByAsc(ConfirmOrderEntity::getTrainCode) // 按照trainCode升序排列
                // 根据date进行过滤，有值就过滤，没有值就不拼接条件
                .eq(ObjectUtil.isNotEmpty(confirmOrderDto.getDate()),ConfirmOrderEntity::getDate,confirmOrderDto.getDate())
                // 根据trainCode进行过滤，有值就过滤，没有值就不拼接条件
                .eq(ObjectUtil.isNotEmpty(confirmOrderDto.getTrainCode()),ConfirmOrderEntity::getTrainCode,confirmOrderDto.getTrainCode());
        //创建分页对象
        IPage<ConfirmOrderEntity> confirmOrderPage = new Page<>(page,limit);
        //调用分页方法
        IPage<ConfirmOrderEntity> confirmOrderPageData = page(confirmOrderPage,confirmOrderWrapper);
        // 获取查询出来的数据集合
        List<ConfirmOrderEntity> records = confirmOrderPageData.getRecords();
        // 将数据塞到PageVo类中并返回出去
        return new PageVo<ConfirmOrderEntity>()
                .setDataList(records)
                .setPageSize(limit).setCurrentPage(page)
                .setTotal((int) confirmOrderPageData.getTotal())
                .setPages((int) confirmOrderPageData.getPages());
    }

    @Override
    public boolean insertAndUpdateConfirmOrder(ConfirmOrderEntity confirmOrder) {
        // 获取当前的时间 --- 用于创建和修改的时候的时间填充
        DateTime now = DateTime.now();
        // 设置火车车次的修改时间
        confirmOrder.setUpdateTime(now);
        // 判断Id是否为空
        if(ObjectUtil.isNotNull(confirmOrder.getId())){
            // 根据id查询订单信息
            ConfirmOrderEntity confirmOrderEntity = this.getById(confirmOrder.getId());
            // 当前id对应的无订单信息
            if(ObjectUtil.isNull(confirmOrderEntity)){
                throw new ApplicationException(ResultCode.DATA_NOT_EXIST,"当前订单信息不存在");
            }
            // 更新订单信息
            return this.updateById(confirmOrder);
        }
        // 给新增的订单信息设置Id，创建时间和修改时间
        confirmOrder.setId(SnowUtil.getSnowflakeNextId())
                .setCreateTime(now);
        // 新增火车车次
        return this.save(confirmOrder);
    }

    @SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
    @Override
    public boolean doConfirm(ConfirmOrderDto confirmOrderDto) {
        // 获取当前登录会员的id
        Long memberId = confirmOrderDto.getMemberId();

        // 获取车次分布式锁
        // 使用redisson分布式锁进行加锁 --- 加锁维度：日期 + 火车编码 + 用户
        String lockKey = RedissonKeyPreEnum.CONFIRM_ORDER.getCode() + "-" + confirmOrderDto.getDate() + "-" + confirmOrderDto.getTrainCode();
        log.info("令牌校验时候的redisson的key");
        RLock lock = null;
        try{
            // 获取锁的对象
            lock = redissonClient.getLock(lockKey);
            // 尝试加锁
            boolean tryLock = lock.tryLock(5, TimeUnit.SECONDS);
            if(tryLock){
                // 加锁成功
                log.info("key为：{}的分布式锁加锁成功",lockKey);
            }else{
                log.info("没抢到锁，有其他线程正在进行消费出票，不做任何处理");
                return false;
//                log.error("key为：{}的分布式锁加锁失败",lockKey);
//                throw new ApplicationException(ResultCode.REDISSON_TRY_LOCK_FAIL,"加锁失败");
            }

            // todo 成功拿到锁后，执行while发放进行循环，排队出票
            while(true){
                // 从数据库查询对应的订单
                LocalDate date = confirmOrderDto.getDate();
                String trainCode = confirmOrderDto.getTrainCode();
                String start = confirmOrderDto.getStart();
                String end = confirmOrderDto.getEnd();
                List<ConfirmOrderTicket> confirmOrderTickets = confirmOrderDto.getConfirmOrderTickets();
                String seatTypeCode = confirmOrderTickets.get(0).getSeatTypeCode();

                // 从数据库查询订单数据 --- 需要进行分页查询
                LambdaQueryWrapper<ConfirmOrderEntity> confirmOrderQWrapper = new LambdaQueryWrapper<ConfirmOrderEntity>()
                        .eq(ConfirmOrderEntity::getDate,date)
                        .eq(ConfirmOrderEntity::getTrainCode,trainCode)
                        .eq(ConfirmOrderEntity::getStatus,ConfirmOrderStatusEnum.INIT)
                        .orderByAsc(ConfirmOrderEntity::getId)
                        ;
                //创建分页对象
                IPage<ConfirmOrderEntity> confirmOrderEntityPage = new Page<>(1,10);
                //调用分页方法
                IPage<ConfirmOrderEntity> confirmOrderEntityPageData = page(confirmOrderEntityPage,confirmOrderQWrapper);
                // 获取查询出来的数据集合
                List<ConfirmOrderEntity> records = confirmOrderEntityPageData.getRecords();
                if(records.size() == 0){
                    log.info("找不到原始订单，结束");
                    break;
                }
                log.info("查询到的订单条数：{}",records.size());
                for (ConfirmOrderEntity confirmOrderEntity : records) {
                    // todo 循环调用出票的方法 --- 同时需要修改出票方法的参数
                    try {
                        this.sellTicket(null);
                    } catch (Exception e) {
                        // todo 判断当前订单余票不足的时候
//                        if("余票不足"){
//                            // todo 将当前的订单状态修改为空，没有余票了
//                        }else{
//                            throw e;
//                        }
                    }
                }
            }

        } catch (Exception e) {
            log.error("key为：{}的分布式锁加锁时候发生异常",lockKey);
            return false;
        }finally {
            log.info("购票流程结束，释放锁");
            // 无论是否正常执行完，都需要将锁释放了
            // 锁为空或者，锁不是当前的线程不进行锁的释放
            if(lock != null && lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
        return true;
    }

    /**
     * 售票
     * @param confirmOrderDto 确认订单的dto
     * @return 是否售票成功
     */
    private void sellTicket(ConfirmOrderDto confirmOrderDto) {
        LocalDate date = confirmOrderDto.getDate();
        String trainCode = confirmOrderDto.getTrainCode();
        String start = confirmOrderDto.getStart();
        String end = confirmOrderDto.getEnd();
        List<ConfirmOrderTicket> confirmOrderTickets = confirmOrderDto.getConfirmOrderTickets();
        String seatTypeCode = confirmOrderTickets.get(0).getSeatTypeCode();
        // 从数据库查询订单数据
        LambdaQueryWrapper<ConfirmOrderEntity> confirmOrderQWrapper = new LambdaQueryWrapper<ConfirmOrderEntity>()
                .eq(ConfirmOrderEntity::getDate,date)
                .eq(ConfirmOrderEntity::getTrainCode,trainCode)
                .eq(ConfirmOrderEntity::getStatus,ConfirmOrderStatusEnum.INIT)
                .orderByAsc(ConfirmOrderEntity::getId)
                ;
        List<ConfirmOrderEntity> list = list(confirmOrderQWrapper);
        if(list.size() == 0){
            log.info("找不到原始订单，结束");
            return ;
        }
        log.info("查询到的订单条数：{}",list.size());
        ConfirmOrderEntity confirmOrderEntity = list.get(0);



        // todo 准备出票之前，将订单的状态改为"处理中"


        // 3.查出余票记录，需要得到真实库存
        DailyTrainTicketEntity trainTicket = dailyTrainTicketService.getTrainTicketByUniqueKey(date, trainCode, start, end);
        log.info("查出余票记录:{}",trainTicket);
        // 4.扣减余票数量，并判断余票是否足够（预扣减）
        reduceTicket(confirmOrderTickets, trainTicket);
        // 5.选座:一个车厢一个车厢的获取座位数据 --- 挑选符合条件的座位，如果这个车厢不满足，则进入下一个车厢（多个选座应该在同一个车厢）
        // 最终地选作结果列表的初始化
        ArrayList<DailyTrainSeatEntity> finalSeatList = new ArrayList<>();
        // 判断乘客是否选座
        String seat = confirmOrderTickets.get(0).getSeat();
        if(StrUtil.isBlank(seat)){
            // 没有选座
            log.info("本次购票没有选座");
            // 循环车票进行选座
            for (int i = 0; i < confirmOrderTickets.size(); i++) {
                selectSeat(finalSeatList,date,trainCode,seatTypeCode,null,null,trainTicket.getStartIndex(),trainTicket.getEndIndex());
            }
        }else{
            log.info("本次购票有选座");
            // 查询本次选座的座位类型都有哪些列，用于计算所选座位于第一个座位的偏移值
            List<SeatColEnum> seatColEnums = SeatColEnum.getColsByType(seatTypeCode);
            log.info("本次选座的座位类型包含的列:{}",seatColEnums);
            // 构造一个和前端类似的选座的参照列表
            ArrayList<String> referToSeatList = new ArrayList<>();
            for (int i = 1; i <= 2 ; i++) {
                for (SeatColEnum seatColEnum : seatColEnums) {
                    referToSeatList.add(seatColEnum.getCode() + i);
                }
            }
            log.info("用于做参照的两排座位:{}",referToSeatList);
            // 计算绝对偏移值
            ArrayList<Integer> absoluteOffsetList = new ArrayList<>();
            for (ConfirmOrderTicket confirmOrderTicket : confirmOrderTickets) {
                // 获取参照选座列表的中对应元素的下标值,并添加到绝对偏移值的列表中
                absoluteOffsetList.add(referToSeatList.indexOf(confirmOrderTicket.getSeat()));
            }
            log.info("计算的到所有座位的绝对偏移值列表:{}",absoluteOffsetList);
            // 计算相对偏移值
            ArrayList<Integer> offsetList = new ArrayList<>();
            for (Integer index : absoluteOffsetList) {
                // 用当前元素 - 绝对偏移值的第一个元素 = 相对偏移值
                offsetList.add(index -  absoluteOffsetList.get(0));
            }
            log.info("计算的到所有座位的相对偏移值列表:{}",offsetList);
            selectSeat(finalSeatList,date,trainCode,seatTypeCode,seat.substring(0,seat.length()-1),offsetList,trainTicket.getStartIndex(),trainTicket.getEndIndex());
        }
        log.info("最终的选座:{}",finalSeatList);
        // 6.选中座位后事务的处理
        // 座位表的修改售卖情况 sell 字段
        // 余票详情表修改余票
        // 为会员增加购票记录
        // 更新确认订单表的状态为 "成功"
        try {
            afterConfirmOrderTransactional.afterDoConfirm(trainTicket,finalSeatList,confirmOrderTickets,confirmOrderEntity);
        } catch (Exception e) {
            log.error("保存购票信息失败：{}",e.getMessage());
            throw new ApplicationException(ResultCode.SAVE_TICKET_INFO_FAIL,"保存购票信息失败");
        }
    }

    @Override
    public void beforeConfirmOrder(ConfirmOrderDto confirmOrderDto) {
        beforeConfirmOrder.beforeDoConfirm(confirmOrderDto);
    }

    /**
     * 挑座位，如果有选座：则一次性挑完， 如果没有选座：则一个一个的挑选
     * @param finalSeatList 最终地选座的列表
     * @param date 日期
     * @param trainCode 火车编号
     * @param seatType 座位类型
     * @param column 座位的列数(不选座就为null)
     * @param offsetList 选座的相对偏移量(不选座就为null)
     * @param startIndex 购票的起始站
     * @param endIndex 购票的结束站
     */
    private void selectSeat(ArrayList<DailyTrainSeatEntity> finalSeatList,LocalDate date, String trainCode, String seatType,String column,ArrayList<Integer> offsetList,Integer startIndex,Integer endIndex){
        // 临时选座的座位列表
        ArrayList<DailyTrainSeatEntity> getSeatList = new ArrayList<>();
        List<DailyTrainCarriageEntity> carriageList = dailyTrainCarriageService.selectBySeatType(date, trainCode, seatType);
        log.info("共查出{}个符合条件的车箱",carriageList.size());
        // 一个车厢一个车厢的获取座位数据
        for (DailyTrainCarriageEntity dailyTrainCarriageEntity : carriageList) {
            getSeatList.clear();
            Integer carriageIndex = dailyTrainCarriageEntity.getCarriageIndex();
            log.info("开始从车厢{}选座", carriageIndex);
            List<DailyTrainSeatEntity> seatList = dailyTrainSeatService.selectByCarriage(date, trainCode, carriageIndex);
            log.info("车厢{}的座位数:{}",carriageIndex,seatList.size());
            // 循环座位
            for (DailyTrainSeatEntity seatEntity : seatList) {
                // 判断column，有值的话就去比对列号
                String seatCol = seatEntity.getSeatCol();
                Integer carriageSeatIndex = seatEntity.getCarriageSeatIndex();
                // 判断当前座位不能被选中过
                boolean alreadyChooseFlag = false;
                for (DailyTrainSeatEntity finalSeat : finalSeatList) {
                    if(finalSeat.getId().equals(seatEntity.getId())){
                        alreadyChooseFlag = true;
                         break;
                    }
                }
                if(alreadyChooseFlag){
                    log.info("座位{}被选中过，不能重复选中，继续判断下一个座位",carriageSeatIndex);
                    continue;
                }
                if(!StrUtil.isBlank(seatCol)){
                    // 判断column
                    if(!seatCol.equals(column)){
                        log.info("座位{}列不对，继续判断下一个座位，当前列值：{}，目标值{}", carriageSeatIndex,seatCol,column);
                        continue;
                    }
                }
                log.info("无选座");

                // 计算是否售卖
                boolean isChoose = calculateSeatSell(seatEntity, startIndex, endIndex);
                // 判断是否被选中
                if(!isChoose){
                    log.info("未选中座位");
                    continue;
                }
                log.info("选中座位");
                getSeatList.add(seatEntity);

                // 根据offSet去选剩下的座位
                boolean isGetAllOffsetSeat = true;
                if(CollUtil.isNotEmpty(offsetList)){
                    log.info("有偏移值：{}，校验偏移值的座位是否可选",offsetList);
                    for (int i = 1; i < offsetList.size(); i++) {
                        Integer offSet = offsetList.get(i);
                        // 计算偏移后的座位索引
                        int nextIndex = carriageSeatIndex + offSet - 1;
                        // 如果有选座，那么一定是在同一个车厢内
                        if(nextIndex >= seatList.size()){
                            log.info("座位{}不可选,偏移后的索引超出了这个车厢的座位数",nextIndex);
                            isGetAllOffsetSeat = false;
                            break;
                        }
                        DailyTrainSeatEntity nextTrainSeatEntity = seatList.get(nextIndex);
                        // 计算是否售卖
                        boolean isChooseNext = calculateSeatSell(nextTrainSeatEntity, startIndex, endIndex);
                        // 判断是否被选中
                        if(!isChooseNext){
                            log.info("座位{}不可选",nextTrainSeatEntity.getCarriageSeatIndex());
                            isGetAllOffsetSeat = false;
                            break;

                        }
                        log.info("座位{}被选中",nextTrainSeatEntity.getCarriageSeatIndex());
                        getSeatList.add(nextTrainSeatEntity);
                    }
                }
                // 判断是否所有的offset都被选中
                if(!isGetAllOffsetSeat){
                    // 清空临时选座列表
                    getSeatList.clear();
                    continue;
                }
                // 保存选好的座位
                finalSeatList.addAll(getSeatList);
                return;
            }
        }
    }

    /**
     * 计算座位在某区间内是否可售卖
     * @param seatEntity 座位的实体类
     * @param startIndex 起始站的站序
     * @param endIndex 结束站的站序
     * @return 选中就返回true，未选中返回false
     */
    private static boolean calculateSeatSell(DailyTrainSeatEntity seatEntity,Integer startIndex,Integer endIndex) {
        // 获取当前座位的售卖信息
        String sell = seatEntity.getSell();
        // 截取出当前的购票的区间的信息
        String intervalSell = sell.substring(startIndex, endIndex);
        if(Integer.parseInt(intervalSell) > 0){
            log.info("座位{}在本次车站区间{}~{}已售过票，不可选中该座位",seatEntity.getCarriageSeatIndex(),startIndex,endIndex);
            return false;
        }else{
            log.info("座位{}在本次车站区间{}~{}未售过票，可选中该座位",seatEntity.getCarriageSeatIndex(),startIndex,endIndex);
            // 修改当前的座位的售买信息
            String currentSell = intervalSell.replace('0', '1');
            currentSell = StrUtil.fillBefore(currentSell,'0',endIndex);
            currentSell = StrUtil.fillAfter(currentSell,'0',sell.length());
            // 进行位与计算：当前售卖信息 | 库里查出来的sell信息计算 = 该作为售卖后的售票信息
            int newSellInt = NumberUtil.binaryToInt(currentSell) | NumberUtil.binaryToInt(sell);
            // 再将十进制转换为二进制
            String newSell = NumberUtil.getBinaryStr(newSellInt);
            // 对转换后的字符串进行前面的补"0"的操作
            newSell = StrUtil.fillBefore(newSell,'0',sell.length());
            log.info("座位{}被选中，原售票信息：{}，车站区间：{}~{}，即：{}，最终售票信息：{}",seatEntity.getCarriageSeatIndex(),sell,startIndex,endIndex,currentSell,newSell);
            // 将新得售卖信息填充到对象中
            seatEntity.setSell(newSell);
            return true;
        }
    }

//    /**
//     * 业务数据校验，车次是否存在，余票是否存在，车次是否在有效期内，tickets条数>0，同乘客同车次是否已买过票
//     * @param date 车次的日期
//     * @param trainCode 车次的编号
//     * @param start 起始站
//     * @param end 终点站
//     * @param seatTypeCode 座位类型
//     */
//    private void checkBusinessData(LocalDate date, String trainCode, String start, String end, String seatTypeCode) {
//        // 车次是否存在，车次是否在有效期内
//        LambdaQueryWrapper<DailyTrainEntity> wrapper = new LambdaQueryWrapper<DailyTrainEntity>()
//                .eq(DailyTrainEntity::getCode, trainCode)
//                .eq(DailyTrainEntity::getDate, date);
//        DailyTrainEntity train = dailyTrainService.getOne(wrapper);
//        if(ObjectUtil.isNull(train)){
//            throw new ApplicationException(ResultCode.DATA_NOT_EXIST,"当前车次不存在");
//        }
//        //同乘客同车次是否已买过票
//
//
//
//        // 余票是否存在
//        DailyTrainTicketEntity trainTicket = dailyTrainTicketService.getTrainTicketByUniqueKey(date, trainCode, start, end);
//        if(ObjectUtil.isNull(trainTicket)){
//            throw new ApplicationException(ResultCode.DATA_NOT_EXIST,"当前余票不存在");
//        }
//        // tickets条数 > 0
//        // 获取座位类型的中文名称
//        String seatName = Objects.requireNonNull(SeatTypeEnum.getEnumByCode(seatTypeCode)).getDesc();
//        // java17的switch增强写法 --- 使用 case xxx -> {} 的方式，直接在{}里面写逻辑，省略break语句
//        switch(seatName){
//            case "一等座" -> getCountResidueTicket(trainTicket.getYdz(),"一等座的余票数量不足",true);
//            case "二等座" -> getCountResidueTicket(trainTicket.getEdz(),"二等座的余票数量不足",true);
//            case "软卧" -> getCountResidueTicket(trainTicket.getRw(),"软卧的余票数量不足",true);
//            case "硬卧" -> getCountResidueTicket(trainTicket.getYw(),"硬卧的余票数量不足",true);
//        }
//    }

//    /**
//     * 保存确认订单，状态为 "初始"
//     * @param confirmOrderDto 确认订单的dto
//     * @param now 当前时间
//     * @return 将保存的订单返回出去
//     */
//    private ConfirmOrderEntity confirmOrderSave(ConfirmOrderDto confirmOrderDto, DateTime now) {
//        ConfirmOrderEntity confirmOrderEntity = BeanUtil.copyProperties(confirmOrderDto, ConfirmOrderEntity.class);
//        confirmOrderEntity
//                .setId(SnowUtil.getSnowflakeNextId())
//                .setMemberId(confirmOrderDto.getMemberId())
//                .setStatus(ConfirmOrderStatusEnum.INIT.getCode())
//                .setTickets(JSON.toJSONString(confirmOrderDto.confirmOrderTickets))
//                .setCreateTime(now)
//                .setUpdateTime(now);
//        this.save(confirmOrderEntity);
//        return confirmOrderEntity;
//    }

    /**
     * 扣减余票数量，并判断余票是否足够（预扣减）
     * @param confirmOrderTickets 车票集合
     * @param trainTicket 每日车票信息
     */
    private static void reduceTicket(List<ConfirmOrderTicket> confirmOrderTickets, DailyTrainTicketEntity trainTicket) {
        for (ConfirmOrderTicket confirmOrderTicket : confirmOrderTickets) {
            // 订单中座位类型code
            String seatTypeCode = confirmOrderTicket.getSeatTypeCode();
            // 通过工具类获取作为类型对应的座位枚举
            SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
            switch (seatTypeEnum) {
                case YDZ -> trainTicket.setYdz(getCountResidueTicket(trainTicket.getYdz(),"一等座余票不足",false));
                case EDZ -> trainTicket.setEdz(getCountResidueTicket(trainTicket.getEdz(),"二等座余票不足",false));
                case RW -> trainTicket.setRw(getCountResidueTicket(trainTicket.getRw(),"软卧余票不足",false));
                case YW -> trainTicket.setYw(getCountResidueTicket(trainTicket.getYw(),"硬卧余票不足",false));
            }
        }
    }

    /**
     * 获取余票的数量
     * @param ticket 座位类型的数量
     * @param infoMessage 报错的信息提示
     * @param isEqual 比较符是否需要加 "=" true: "<=", false: "<"
     * @return 当前座位的余票数量
     */
    private static int getCountResidueTicket(int ticket, String infoMessage, boolean isEqual) {
        int countResidueTicket = ticket - 1;
        if(isEqual){
            if(ticket <= 0){
                throw new ApplicationException(ResultCode.INSUFFICIENT_REMAINING_TICKETS,infoMessage);
            }
            return ticket;
        }
        if(countResidueTicket < 0){
            throw new ApplicationException(ResultCode.INSUFFICIENT_REMAINING_TICKETS,infoMessage);
        }
        return countResidueTicket;
    }

    /**
     * 被sentinel限流降级的处理发放
     * @param confirmOrderDto 订单确认的参数
     * @param e 降级的异常参数
     */
    public void doConfirmBlock(ConfirmOrderDto confirmOrderDto, BlockException e){
        log.info("购票被限流：{}",confirmOrderDto);
        throw new ApplicationException(ResultCode.SENTINEL_DEAL_BUY_TICKET,"购票方法被sentinel限流了，请稍后在尝试");
    }
}
